﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Lilium.Reflection;
using Lilium.Collections;

namespace Lilium.Web.Mvc.Validation
{
	[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
	public sealed class ValidateAttribute : ActionFilterAttribute, IActionValidator
	{
		/// <summary>
		/// Creates an instance of ValidateActionAttribute.
		/// </summary>
		/// <param name="serviceType">
		/// Type of service containing validation method.
		/// </param>
		/// <param name="serviceMethod">
		/// Validation method.
		/// <remarks>
		/// Validation method should accept action parameters and one additional parameter named "modelErrors" of type IModelErrors.
		/// </remarks>
		/// </param>
		public ValidateAttribute(Type serviceType, string serviceMethod)
		{
			if (serviceMethod == null) throw new ArgumentNullException("serviceMethod");
			if (serviceType == null) throw new ArgumentNullException("serviceType");

			m_ServiceType = serviceType;
			m_ServiceMethod = serviceMethod;
		}

		public override void OnActionExecuting(ActionExecutingContext filterContext)
		{
			if (filterContext == null) throw new ArgumentNullException("filterContext");

			var controller = filterContext.Controller as EntityController;
			if (controller == null)
				throw new InvalidOperationException("ValidateActionAttribute sould be applied to EntityController.");

			var validateActionContext = new ValidateActionContext(controller, filterContext.ActionParameters);
			Validate(validateActionContext);
			validateActionContext.ModelErrors.CopyToModelState(controller.ModelState);
		}

		#region IActionValidator Members

		public void Validate(ValidateActionContext context)
		{
			if (context == null) throw new ArgumentNullException("context");

			var validationService = context.ServiceProvider.GetService(ServiceType, true);
			var actionParameters = new Dictionary<string, object>(context.ActionParameters);
			actionParameters["modelErrors"] = context.ModelErrors;
			validationService.DynamicInvoke(ServiceMethod, actionParameters);
		}

		#endregion

		public override object TypeId
		{
			get
			{
				return Tuple.Create(ServiceMethod, ServiceType);
			}
		}

		#region private string ServiceMethod

		private readonly string m_ServiceMethod;

		private string ServiceMethod
		{
			get
			{
				return m_ServiceMethod;
			}
		}

		#endregion

		#region private Type ServiceType

		private readonly Type m_ServiceType;

		private Type ServiceType
		{
			get
			{
				return m_ServiceType;
			}
		}

		#endregion
	}
}